﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.Windows.Controls;

namespace Sinacor.Infra.UI.Controls.SmartClient
{
	internal static class DrillDownAnimation
	{
		private const double _forwardDuration = 700;
		private const double _backwardDuration = 300;
		private const double _moveDuration = 700;
		private static int _contadorIteracao = 1;
		private static bool _isAnimating;
		private static TranslateTransform3D _currentTranslate;

		/// <summary>
		/// Propriedade que retorna se alguma animação está executando
		/// </summary>
		public static bool IsAnimating
		{
			get { return DrillDownAnimation._isAnimating; }
		}

		/// <summary>
		/// Movimenta o primeiro elemento para a frente(em relação à camera)
		/// </summary>
		/// <param name="childViewport2D">Elemento filho ao qual o atual deve se basear</param>
		/// <param name="actualViewport2D">Elemento que se deseja movimentar em relação ao elemento filho</param>
		public static void MoveTopElementForward(Viewport2DVisual3D childViewport2D, Viewport2DVisual3D actualViewport2D)
		{
			_isAnimating = true;

			double mainOffsetX = ((TranslateTransform3D)((Transform3DGroup)childViewport2D.Transform).Children[0]).OffsetX;
			double mainOffsetY = ((TranslateTransform3D)((Transform3DGroup)childViewport2D.Transform).Children[0]).OffsetY;

			mainOffsetX = mainOffsetX == 0 ? 1 : mainOffsetX;
			mainOffsetY = mainOffsetY == 0 ? -1 : mainOffsetY;

			double xsin = -mainOffsetX / Math.Sqrt(Math.Pow(mainOffsetX,2));
			double ysin = -mainOffsetY / Math.Sqrt(Math.Pow(mainOffsetY,2));

			TranslateTransform3D translateTransform3D = (TranslateTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[0] as TranslateTransform3D;

			double offsetX = translateTransform3D.OffsetX + (xsin * (3 * (Math.Cos(0.5 * _contadorIteracao) - 0.1)));
			double offsetY = translateTransform3D.OffsetY + (ysin * (2.1 * (Math.Sin(0.5 * _contadorIteracao) - 0.1)));
			double offsetZ = translateTransform3D.OffsetZ - (7 * 0.9);

			_currentTranslate = translateTransform3D;

			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty, MakeAnimation(offsetX, _forwardDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetYProperty, MakeAnimation(offsetY, _forwardDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(offsetZ, _forwardDuration, Animation_Completed));

			_contadorIteracao++;
		}

		/// <summary>
		/// Movimenta todos os elementos para frente(em relação à camera) exceto o primeiro elemento, que é movimentado por outra função
		/// </summary>
		/// <param name="previousViewport2D">Elemento anterior ao elemento que se deseja movimentar</param>
		/// <param name="actualViewport2D">Elemento que irá se movimentar</param>
		public static void MoveRemainingElementsForward(Viewport2DVisual3D previousViewport2D, Viewport2DVisual3D actualViewport2D)
		{
			_isAnimating = true;

			TranslateTransform3D previousTranslateTransform3D = ((TranslateTransform3D)((Transform3DGroup)previousViewport2D.Transform).Children[0]);
			TranslateTransform3D translateTransform3D = ((TranslateTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[0]);

			_currentTranslate = translateTransform3D;

			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty, MakeAnimation(previousTranslateTransform3D.OffsetX, _forwardDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetYProperty, MakeAnimation(previousTranslateTransform3D.OffsetY, _forwardDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(previousTranslateTransform3D.OffsetZ, _forwardDuration, null));
		}

		/// <summary>
		/// Movimenta o elemento clickado para o posição inicial
		/// </summary>
		/// <param name="translate">Posicao do elemento pai atual</param>
		/// <param name="actualViewport2D">Elemento que irá se movimentar para a posição do pai</param>
		/// <param name="freeAnimation">Quando true irá disparar o evento informando o término da animação</param>
		public static void MoveElementToParentPosition(TranslateTransform3D translate, Viewport2DVisual3D actualViewport2D, bool freeAnimation)
		{
			_isAnimating = true;

			TranslateTransform3D translateTransform3D = ((TranslateTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[0]);

			_currentTranslate = translateTransform3D;

			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty, MakeAnimation(translate.OffsetX, _backwardDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetYProperty, MakeAnimation(translate.OffsetY, _backwardDuration, null));

			if(freeAnimation)
				translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(translate.OffsetZ, _backwardDuration, Animation_Completed));
			else
				translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(translate.OffsetZ, _backwardDuration, null));

			_contadorIteracao = 1;
		}

		/// <summary>
		/// Movimenta todos os elementos para trás(em relação à camera)
		/// </summary>
		/// <param name="translate">Posição inicial que servirá como base de relação para todos os elementos</param>
		/// <param name="actualViewport2D">Elemento que irá se movimentar</param>
		/// <param name="iteration">Numero de vezes que o calculo de posição será processado</param>
		/// <param name="freeAnimation">Quando true irá disparar o evento informando o término da animação</param>
		public static void MoveElementsBackwards(TranslateTransform3D translate, Viewport2DVisual3D actualViewport2D, int iteration, bool freeAnimation)
		{
			_isAnimating = true;
			TranslateTransform3D translateTransform3D = ((TranslateTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[0]);

			_currentTranslate = translateTransform3D;

			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty, MakeAnimation(MathCoordinateX(translate.OffsetX, iteration), _backwardDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetYProperty, MakeAnimation(MathCoordinateY(translate.OffsetY, iteration), _backwardDuration, null));
			
			if(freeAnimation)
				translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(MathCoordinateZ(translate.OffsetZ, iteration), _backwardDuration, Animation_Completed));
			else
				translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(MathCoordinateZ(translate.OffsetZ, iteration), _backwardDuration, null));
			_contadorIteracao++;
		}

		/// <summary>
		/// Movimenta o primeiro elemento para a posição máxima à esquerda
		/// </summary>
		/// <param name="actualViewport2D">Elemento que irá se movimentar</param>
		public static void MoveElementToRightBoundary(Viewport2DVisual3D actualViewport2D)
		{
			_isAnimating = true;
			TranslateTransform3D translateTransform3D = ((TranslateTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[0]);
			ScaleTransform3D scale = ((ScaleTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[1]);

			_currentTranslate = translateTransform3D;

			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty, MakeAnimation((1 / scale.ScaleX - (1.1)), _moveDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetYProperty, MakeAnimation(0, _moveDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(0, _moveDuration, Animation_Completed));
		}

		/// <summary>
		/// Movimenta o primeiro elemento para a posição máxima à direita
		/// </summary>
		/// <param name="actualViewport2D">Elemento que irá se movimentar</param>
		public static void MoveElementToLeftBoundary(Viewport2DVisual3D actualViewport2D)
		{
			_isAnimating = true;
			TranslateTransform3D translateTransform3D = ((TranslateTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[0]);
			ScaleTransform3D scale = ((ScaleTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[1]);

			_currentTranslate = translateTransform3D;

			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty, MakeAnimation((-1 / scale.ScaleX + (1.1)), _moveDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetYProperty, MakeAnimation(0, _moveDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(0, _moveDuration, Animation_Completed));
		}

		/// <summary>
		/// Movimenta o elemento para as coordenadas especificadas
		/// </summary>
		/// <param name="actualViewport2D">Elemento que irá se movimentar</param>
		/// <param name="offsetX">Posição no eixo X ao qual se deseja enviar o elemento</param>
		/// <param name="offsetY">Posição no eixo Y ao qual se deseja enviar o elemento</param>
		public static void MoveElementToDesiredStartPosition(Viewport2DVisual3D actualViewport2D, double offsetX, double offsetY, double animationDuration)
		{
			_isAnimating = true;

			if (animationDuration == 0)
				animationDuration = _forwardDuration;

			TranslateTransform3D translateTransform3D = ((TranslateTransform3D)((Transform3DGroup)actualViewport2D.Transform).Children[0]);

			_currentTranslate = translateTransform3D;

			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty, MakeAnimation(offsetX, animationDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetYProperty, MakeAnimation(offsetY, animationDuration, null));
			translateTransform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, MakeAnimation(0, animationDuration, Animation_Completed));
		}

		/// <summary>
		/// Remove as animações que estão vinculadas à propriedades de dependência
		/// </summary>
		public static void CleanAnimationProperties()
		{
			_currentTranslate.BeginAnimation(TranslateTransform3D.OffsetXProperty, null);
			_currentTranslate.BeginAnimation(TranslateTransform3D.OffsetYProperty, null);
			_currentTranslate.BeginAnimation(TranslateTransform3D.OffsetZProperty, null);
		}

		/// <summary>
		/// Cria e retorna uma animação
		/// </summary>
		/// <param name="to">O valor de destino da animação</param>
		/// <param name="duration">O tempo que a animação irá levar do começo ao fim uma vez</param>
		/// <param name="endEvent">Evento que será disparado ao terminar a animação quando desejado</param>
		/// <returns>Animção que foi criada</returns>
		private static DoubleAnimation MakeAnimation(double to, double duration, EventHandler endEvent)
		{
			DoubleAnimation anim = new DoubleAnimation(to, TimeSpan.FromMilliseconds(duration));
			anim.AccelerationRatio = 0.2;
			anim.DecelerationRatio = 0.7;
			
			if (endEvent != null)
				anim.Completed += endEvent;
			return anim;
		}

		/// <summary>
		/// Função que é chamada quando o evento "Completed" da classe "Animation" é disparado
		/// </summary>
		private static void Animation_Completed(object sender, EventArgs e)
		{
			_isAnimating = false;
		}

		/// <summary>
		/// calcula as coordenadas "filhas" de um dado ponto no eixo X
		/// </summary>
		/// <param name="startPosition">Ponto do eixo X que será usado como base para se fazer o cálculo</param>
		/// <param name="iterationCounter">Numero de vezes que o calculo deve ser feito</param>
		/// <returns>Posição final calculada</returns>
		private static double MathCoordinateX(double startPosition, int iterationCounter)
		{
			double Resultado = startPosition;

			startPosition = startPosition == 0 ? 1 : startPosition;
			double xsin = -startPosition / Math.Sqrt(Math.Pow(startPosition, 2));

			while (iterationCounter > 0)
			{
				Resultado += ((xsin) * (1.7 * (Math.Cos(0.5 * (_contadorIteracao - iterationCounter + 1)) - 0.1)));
				iterationCounter--;
			}

			return Resultado;
		}

		/// <summary>
		/// calcula as coordenadas "filhas" de um dado ponto no eixo Y
		/// </summary>
		/// <param name="startPosition">Ponto do eixo Y que será usado como base para se fazer o cálculo</param>
		/// <param name="iterationCounter">Numero de vezes que o calculo deve ser feito</param>
		/// <returns>Posição final calculada</returns>
		private static double MathCoordinateY(double startPosition, int NumeroVezes)
		{
			double Resultado = startPosition;

			startPosition = startPosition == 0 ? -1 : startPosition;
			double ysin = -startPosition / Math.Sqrt(Math.Pow(startPosition, 2));

			while (NumeroVezes > 0)
			{
				Resultado += ((ysin) * (2 * (Math.Sin(0.5 * (_contadorIteracao - NumeroVezes + 1)) - 0.1)));
				NumeroVezes--;
			}
			return Resultado;
		}

		/// <summary>
		/// calcula as coordenadas "filhas" de um dado ponto no eixo Z
		/// </summary>
		/// <param name="startPosition">Ponto do eixo Z que será usado como base para se fazer o cálculo</param>
		/// <param name="iterationCounter">Numero de vezes que o calculo deve ser feito</param>
		/// <returns>Posição final calculada</returns>
		private static double MathCoordinateZ(double startPosition, int NumeroVezes)
		{
			double Resultado = startPosition;
			while (NumeroVezes > 0)
			{
				Resultado += (-(7 * 0.9));
				NumeroVezes--;
			}
			return Resultado;
		}
	}
}  